<?php
/**
 * création d'une liste pour les mouvements
 *
 * @version 1.0
 * @author jonathan poelen <jonathan.poelen@gmail.com>
 * @date 10-22-2010
 */
class SW_Move_List_Element
{
	/** @var SW_Move_List_Element|null */
	public $next, $prev;

	/** @var SW_Move_List_Element|null */
	public $move;


	public function __construct(SW_Move $move = null, SW_Move_List_Element $prev = null, SW_Move_List_Element $next = null)
	{
		$this->move = $move;
		$this->prev = $prev;
		$this->next = $next;
		if ($next)
			$next->prev = $this;
		if ($prev)
			$prev->next = $this;
	}

	/**
	 * @return SW_Move
	 */
	static public function detach(SW_Move_List_Element $list)
	{
		if ($list->next)
		{
			$this->next->prev = $list->prev;
			$list->next = null;
		}
		if ($list->prev)
		{
			$list->prev->next = $list->next;
			$list->prev = null;
		}
		$move = $list->move;
		unset($list);
		return $move;
	}

	/**
	 * @param SW_Move_List_Element $list
	 */
	public function attachNext(SW_Move_List_Element $list)
	{
		$this->next = $list;
		$list->prev = $this;
	}

	/**
	 * @param SW_Move_List_Element $list
	 */
	public function attachPrev(SW_Move_List_Element $list)
	{
		$this->prev = $list;
		$list->next = $this;
	}

	/**
	 * @param SW_Move_List_Element $list
	 */
	public function addNext(SW_Move_List_Element $list)
	{
		$e = $this->next;
		$this->attachNext($list);
		$list->getLast()->attachNext($e);
	}

	/**
	 * Récupére le mouvement à la position donnée.
	 * @param int $position
	 * @return SW_Move_List_Element|null
	 */
	final public function getPosition($position)
	{
		if (0 > $position)
			return null;
		$e = $this;
		while ($position-- && $e)
			$e = $e->next;
		return $e;
	}

	/**
	 * @return SW_Move_List_Element
	 */
	final public function getLast()
	{
		$e = $this;
		while ($e->next)
			$e = $e->next;
		return $e;
	}
}

/**
 * Conteneur de liste pour les mouvement
 *
 * @version 1.0
 * @author jonathan poelen <jonathan.poelen@gmail.com>
 * @date 10-22-2010
 */
class SW_Move_Access implements SW_Move_Access_Interface, SeekableIterator, ArrayAccess, Countable
{
	/** @var int */
	protected $count = 0;

	/** @var SW_Move_List_Element|null */
	protected $begin = null;

	/** @var SW_Move_List_Element|null */
	protected $last = null;


	/** @var SW_Move_List_Element|null */
	private $it = null;

	/** @var int */
	private $posIt = 0;


	/**
	 * @param array|SW_Move $move = null
	 */
	 public function __construct($move = null)
	 {
		if (!empty($move))
		{
			if ($move instanceof SW_Move)
			{
				$this->count = 0;
				$this->begin = $this->last = new SW_Move_List_Element($move);
			}
			else
			{
				$this->count = count($move);
				$this->begin = $this->last = new SW_Move_List_Element(array_unshift($move));
				foreach ($move as $m)
					$this->last = new SW_Move_List_Element($m, $this->last, null);
			}
		}
	}

	/**
	 * remise à zéro de l'itérateur
	 */
	public function rewind()
	{
		$pos = 0;
		$this->it = $this->begin;
	}

	/**
	 */
	public function next()
	{
		++$this->posIt;
		$this->it = $this->it->next;
	}

	/**
	 */
	public function prev()
	{
		--$this->posIt;
		$this->it = $this->it->prev;
	}

	/**
	 */
	public function key()
	{
		return $this->posIt;
	}

	/**
	 * @return bool
	 */
	public function valid()
	{
		return null !== $this->it;
	}

	/**
	 * @return SW_Move
	 */
	public function current()
	{
		return $this->it->move;
	}

	/**
	 * @param SW_Move $move
	 */
	public function offsetCurent(SW_Move $move)
	{
		$this->it->move = $move;
	}

	/**
	 * Ajoute en fin de la liste
	 * @param SW_Move $move
	 */
	public function push(SW_Move $move)
	{
		$this->last = new SW_Move_List_Element($move, $this->last, null);
		if (null === $this->begin)
			$this->begin = $this->last;
		++$this->count;
	}

	/**
	 * Ajoute au début de la liste
	 * @param SW_Move $move
	 */
	public function unshift(SW_Move $move)
	{
		$this->begin = new SW_Move_List_Element($move, null, $this->begin);
		if (null === $this->last)
			$this->last = $this->begin;
		++$this->count;
	}

	/**
	 * Supprime le premier mouvement
	 * @return SW_Move|null
	 */
	public function shift()
	{
		if ($e = $this->begin)
		{
			--$this->count;
			if ($e === $this->last)
			{
				$this->begin = $this->last = null;
				return $e->move;
			}
			return SW_Move_List_Element::detach($e);
		}
		return null;
	}

	/**
	 * Supprime le dernier mouvement
	 * @return SW_Move|null
	 */
	public function pop()
	{
		if ($e = $this->last)
		{
			--$this->count;
			if ($e === $this->begin)
			{
				$this->begin = $this->last = null;
				return $e->move;
			}
			return SW_Move_List_Element::detach($e);
		}
		return null;
	}

	/**
	 * Ajoute après l'itération
	 * @param SW_Move $move
	 */
	public function insertAfterIteration(SW_Move $move)
	{
		if ($this->it && $this->it !== $this->last)
		{
			new SW_Move_List_Element($move, $this->it, $this->it->next);
			++$this->count;
		}
		else
			$this->push($move);
	}

	/**
	 * Ajoute avant l'itération
	 * @param SW_Move $move
	 */
	public function insertBeforeIteration(SW_Move $move)
	{
		if ($this->it && $this->it !== $this->begin)
		{
			new SW_Move_List_Element($move, $this->it->prev, $this->it);
			++$this->count;
			++$this->posIt;
		}
		else
			$this->unshift($move);
	}

	/**
	 * Supprime l'élément itérer et passe au suivant
	 * @return SW_Move|null
	 */
	public function detachIteration()
	{
		if ($this->it)
		{
			if ($this->it === $this->begin)
			{
				$this->it = $this->it->next;
				return $this->shift();
			}
			if ($this->it === $this->last)
			{
				$this->it = null;
				return $this->pop();
			}
			--$this->count;
			$next = $this->it->next;
			return SW_Move_List_Element::detach($this->it);
			$this->it = $next;
		}
		return null;
	}

	/**
	 * @return bool
	 */
	public function isEmpty()
	{
		return (bool)$this->count;
	}

	/**
	 * Ajoute à la position donnée
	 * @param array|SW_Move $move
	 * @param int $position
	 * @return int : nombre d'élément ajouté
	 */
	public function insert($move, $position)
	{
		if (0 > $position)
			$position = $this->count - $position;
		if ($this->positionIsValid($position))
		{
			if ($move instanceof SW_Move)
			{
				if (0 == $position)
					$this->unshift($move);
				else if ($this->count === $position + 1)
					$this->push($move);
				else
				{
					$e = $this->begin->getPosition($position - 1);
					new SW_Move_List_Element($move, $e, $e->next);
					++$this->count;
				}
				return 1;
			}
			if (empty($move))
				return 0;
			$count = count($move);
			$begin = $last = new SW_Move_List_Element(array_unshift($move));
			foreach ($move as $m)
				$last = new SW_Move_List_Element($m, $last, null);
			if (null === $this->begin)
			{
				$this->begin = $begin;
				$this->last = $last;
				return $this->count = $count;
			}
			else if (0 == $position)
			{
				$last->attachNext($this->last);
				$this->begin = $begin;
			}
			else if ($this->count === $position + 1)
			{
				$last->attachNext($this->last);
				$this->last = $last;
			}
			else
			{
				$e = $this->begin->getPosition($position - 1);
				$next = $e->next;
				$e->attachNext($begin);
				$last->attachNext($next);
			}
			$this->count += $count;
			return $count;
		}
		return 0;
	}

	/**
	 * @return array
	 */
	public function toArray()
	{
		$e = $this->begin;
		$a = array();
		while ($e)
		{
			$a[] = $e;
			$e = $e->next;
		}
		return $a;
	}

	/**
	 * @param int $position
	 * @return bool
	 */
	final public function positionIsValid($position)
	{
		return 0 <= $position && $position < $this->count;
	}

	/**
	 * Supprime à la position donnée
	 * @param int $position
	 * @return SW_Move|null
	 */
	public function detach($position)
	{
		if ($this->positionIsValid($position))
		{
			if (0 == $position)
				return $this->shift();
			if ($this->count === $position + 1)
				return $this->pop();
			--$this->count;
			return SW_Move_List_Element::detach($this->begin->getPosition($position));
		}
		return null;
	}

	/**
	 * @param int $begin
	 * @param int $end
	 * @param SW_Move_List_Element $e
	 * @param array &$a
	 * @return SW_Move|null
	 */
	final static protected function addForRange($begin, $end, SW_Move_List_Element $e, array &$a)
	{
		while ($begin++ < $end)
		{
			$a[] = $e->move;
			$tmp = $e->next;
			unset($e);
			$e = $tmp;
		}
		return $e;
	}

	/**
	 * Supprime un intervalle de mouvement
	 * @param int $begin
	 * @param int $end
	 * @param bool $endIsCount = false
	 * @return array
	 */
	public function detachRange($begin, $end, $endIsCount = false)
	{
		if (!$this->count)
			return array();
		$begin = max(0, (0 > $begin) ? $this->count - $begin : $begin);
		if ($endIsCount)
			$end += $begin;
		$end = min($this->count, (0 > $end) ? $this->count - $end : $end);

		if ($begin > $end)
		{
			$inv = $begin;
			$begin = $end;
			$end = $inv;
		}
		else
			$inv = null;

		$this->count += $begin - $end;
		if ($this->count === $begin + 1)
			return array($this->pop());
		$a = array();
		if (0 === $begin)
		{
			if ($e = self::addForRange($begin, $end, $this->begin, $a))
			{
				$this->begin = $e;
				$e->prev = null;
			}
			else
				$this->begin = $this->last = null;
		}
		else
		{
			$start = $this->begin->getPosition($begin);
			if ($e = self::addForRange($begin, $end, $start, $a))
				$start->attachNext($e);
			else
			{
				$start->next = null;
				$this->last = $start;
			}
		}
		return $inv ? $a : array_reverse($a);
	}

	/**
	 * @param int $position
	 * @return SW_Move_List_Element|null
	 */
	final protected function getListPosition($position)
	{
		return ($this->positionIsValid($position)) ? $this->begin->getPosition($position) : null;
	}

	/**
	 * Vérifie si l'offset exist
	 * @param int $offset
	 * @return bool
	 */
	public function offsetExists($offset)
	{
		return (bool)$this->getListPosition($offset);
	}

	/**
	 * Retourne l'offset
	 * @param int $offset
	 * @return SW_Move|null
	 */
	public function offsetGet($offset)
	{
		$e = $this->getListPosition($offset);
		return $e ? $e->move : null;
	}

	/**
	 * Retourne l'offset
	 * @param int $offset
	 * @param SW_Move $move
	 */
	public function offsetSet($offset, $move)
	{
		if (null !== $offset && ($e = $this->getListPosition($offset)))
			$e->move = $move;
		else
			$this->push($move);
	}

	/**
	 * Supprime l'offset
	 * @param int $offset
	 */
	public function offsetUnset($offset)
	{
		if ($this->positionIsValid($offset))
		{
			if (0 == $offset)
				$this->shift();
			else if ($this->count === $offset + 1)
				$this->pop();
			else
			{
				$e = $this->begin;
				while ($offset-- && $e)
					$e = $e->next;
				SW_Move_List_Element::detach($e);
				--$this->count;
			}
		}
	}

	/**
	 * Replace la valeur de $e par $move et retourne la précèdente valeur de $e
	 * @param SW_Move_List_Element $e
	 * @param SW_Move $move
	 * @return SW_Move
	 */
	final protected function replaceElement(SW_Move_List_Element $e, SW_Move $move)
	{
		$tmp = $e->move;
		$e->move = $move;
		return $tmp;
	}

	/**
	 * @param int $pos
	 * @param SW_Move $move
	 * @return SW_Move|null
	 */
	public function replace($offset, SW_Move $move)
	{
		if ($this->positionIsValid($offset))
		{
			if (0 == $offset)
				return $this->replaceElement($this->begin, $move);
			if ($this->count === $offset + 1)
				return $this->replaceElement($this->last, $move);
			else
			{
				$e = $this->begin;
				while ($offset-- && $e)
					$e = $e->next;
				return $this->replaceElement($e, $move);
			}
		}
		return null;
	}

	/**
	 * positionne l'itérateur à la position donnée
	 * @param int $position : si la position est négatif alors la position erst relatif par rapport à la fin
	 */
	public function seek($position)
	{
		if (0 > $position)
			$position = $this->count - $position;
		if ($e = $this->getListPosition($position))
		{
			$this->posIt = $position;
			$this->it = $e;
		}
		else
			$this->it = null;
	}

	/**
	 * @return int
	 */
	public function count()
	{
		return $this->count;
	}

	/**
	 * @param SW_Move $move
	 * @return int : -1 si non trouvé
	 */
	public function indexOf(SW_Move $move)
	{
		$index = 0;
		$it = $this->begin;
		while ($it)
		{
			if ($it === $move)
				return $index;
			$it = $it->net;
			++$index;
		}
		return -1;
	}

	/**
	 * Utilise isArrive() du 1er mouvement. Retourne null si aucun mouvement n'existe
	 * @return bool|null
	 */
	public function firstMoveIsArrrive()
	{
		return $this->begin ? $this->begin->isArrive() : null;
	}
}